package main

import (
	"errors"
	"github.com/xgdapg/wtk"
	"reflect"
	"strings"
	"sync"
)

var pluginManager = &PluginManager{
	plugins:    make(map[string]PluginInterface),
	pluginLock: new(sync.Mutex),
	hooks:      make(map[string][]*Hook),
	hookLock:   new(sync.Mutex),
}

type Hook struct {
	Name string
	Func wtk.HookHandlerFunc
}

type PluginManager struct {
	plugins    map[string]PluginInterface
	pluginLock *sync.Mutex
	hooks      map[string][]*Hook
	hookLock   *sync.Mutex
}

func getTypeName(i interface{}) string {
	s := strings.Split(reflect.TypeOf(i).String(), ".")
	if len(s) != 2 {
		return ""
	}
	return s[1]
}

func (this *PluginManager) RegisterPlugin(plugin PluginInterface) error {
	typeName := getTypeName(plugin)
	if !strings.HasPrefix(typeName, "Plugin") {
		return errors.New("plugin type name error")
	}
	pluginName := typeName[6:]

	rv := reflect.Indirect(reflect.ValueOf(plugin))

	nameField := rv.FieldByName("Name")
	if nameField.Kind() != reflect.String {
		return errors.New("plugin name field not found")
	}
	nameField.SetString(pluginName)

	runningField := rv.FieldByName("Running")
	if runningField.Kind() != reflect.Bool {
		return errors.New("plugin running field not found")
	}
	runningField.SetBool(false)

	dataField := rv.FieldByName("Data")
	if dataField.Kind() != reflect.String {
		return errors.New("plugin data field not found")
	}
	dataField.SetString("")

	this.pluginLock.Lock()
	defer this.pluginLock.Unlock()

	if _, ok := this.plugins[pluginName]; ok {
		return errors.New("plugin name already in use")
	}

	this.plugins[pluginName] = plugin
	return nil
}

func (this *PluginManager) AddHook(event, name string, hookFunc wtk.HookHandlerFunc) {
	this.hookLock.Lock()
	defer this.hookLock.Unlock()

	if _, ok := this.hooks[event]; !ok {
		this.hooks[event] = []*Hook{}
		wtk.AddHandlerHook(event, func(c *wtk.HookHandler) {
			if hs, ok := this.hooks[event]; ok {
				for _, h := range hs {
					h.Func(c)
				}
			}
		})
	}
	this.hooks[event] = append(this.hooks[event], &Hook{
		Name: name,
		Func: hookFunc,
	})
}

func (this *PluginManager) DeleteHookByName(name string) {
	this.hookLock.Lock()
	defer this.hookLock.Unlock()

	for ev, hs := range this.hooks {
		list := []*Hook{}
		for _, h := range hs {
			if h.Name != name {
				list = append(list, h)
			}
		}
		this.hooks[ev] = list
	}
}

func (this *PluginManager) startPlugins() {
	for pluginName, plugin := range this.plugins {
		pluginData, _ := model.GetPluginByName(pluginName)
		if pluginData == nil {
			pluginData = &PluginData{
				Name:    pluginName,
				Enabled: false,
				Data:    "",
			}
			model.SavePlugin(pluginData)
		}
		plugin.SetData(pluginData.Data)
		// dataField := reflect.Indirect(reflect.ValueOf(plugin)).FieldByName("Data")
		// if dataField.Kind() == reflect.String {
		// 	dataField.SetString(pluginData.Data)
		// }
		if pluginData.Enabled {
			go plugin.Start()
		}
	}
}

type PluginInterface interface {
	Start()
	Stop()
	SetData(string)
}

type Plugin struct {
	Name    string
	Running bool
	Data    string
}

func (this *Plugin) Start() {
	this.Running = true
}

func (this *Plugin) Stop() {
	this.Running = false
	pluginManager.DeleteHookByName(this.Name)
}

func (this *Plugin) SaveData(data string) {
	this.Data = data
	model.SetPluginData(this.Name, this.Data)
}

func (this *Plugin) SetData(data string) {
	this.Data = data
}

func (this *Plugin) RegisterHook(event string, hookFunc wtk.HookHandlerFunc) {
	pluginManager.AddHook(event, this.Name, hookFunc)
}

func (this *Plugin) GetBlock(h *wtk.HookHandler, name string) *PluginBlock {
	block := &PluginBlock{h: h, head: "", body: "", tail: ""}
	b := "<!-- Plugin_Block_" + name + "_Begin -->"
	e := "<!-- Plugin_Block_" + name + "_End -->"
	content := h.Template.GetResultString()
	bp := strings.Index(content, b)
	if bp == -1 {
		return block
	}
	ep := strings.Index(content, e)
	if ep == -1 || ep <= bp {
		return block
	}
	i := bp + len(b)
	block.head = content[:i]
	block.body = content[i:ep]
	block.tail = content[ep:]
	return block
}

type PluginBlock struct {
	h    *wtk.HookHandler
	head string
	body string
	tail string
}

func (this *PluginBlock) save() {
	if this.head == "" || this.tail == "" {
		return
	}
	this.h.Template.SetResultString(this.head + this.body + this.tail)
}

func (this *PluginBlock) Set(content string) {
	this.body = content
	this.save()
}

func (this *PluginBlock) Append(content string) {
	this.body += content
	this.save()
}

func (this *PluginBlock) Prepend(content string) {
	this.body = content + this.body
	this.save()
}
